package MonteCarloMultiThread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import View.MonteCarloGUI;
import View.Updatable;

/**
 * Mono thread calculation for the MonteCarlo Simulation
 * @author adrien
 *
 */

public class MonteCarloSimulation implements Runnable{
	
    final static int numberOfCores = Runtime.getRuntime().availableProcessors();
    final static double blockingCoefficient = 0.9;
    final static int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
	
    final static ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);  
	static List<Callable<Double>> partitions = new ArrayList<Callable<Double>>();
	
	

	private String CallPutFlag; //"c" for call and "p" for put
	private double S,X,T,r,b,v;
	private int nSteps,nSimulations;
	private Updatable viewToUpdate;
	JLabel labelResult;
	JLabel labelTime;
	JLabel labelThread;
	private MonteCarloSimulation that = this;
	
	ConcurrentHashMap<Integer, Integer> threadIDMap = new ConcurrentHashMap<Integer, Integer>();
	private int nbSimuDone = 0;
	
	
	private double result; //to display the final result of the calculation
	private static double timeStart;
	
	
	
	
	
	public MonteCarloSimulation(View.Updatable that, JLabel labelResult,JLabel labelTime,JLabel labelThread, String callPutFlag, double s, double x,
			double t, double r, double b, double v, int nSteps, int nSimulations) {
		super();
		CallPutFlag = callPutFlag;
		S = s;
		X = x;
		T = t;
		this.r = r;
		this.b = b;
		this.v = v;
		this.nSteps = nSteps;
		this.nSimulations = nSimulations;
		this.viewToUpdate = that;
		this.labelResult = labelResult;
		this.labelTime = labelTime;
		this.labelThread = labelThread;
	}

	@Override
	public void run() {
		
		viewToUpdate.addLabelWest(labelResult);
		viewToUpdate.addLabelEast(labelTime);
		viewToUpdate.addLabelCenter(labelThread);
		viewToUpdate.setProgressBar(0);
		
		
		double dt,Sum =0, Drift, vSqrdt;
		int z=0;
		
		timeStart = System.currentTimeMillis(); //initialize the time
		
		
		
		dt = T / nSteps ;
		Drift = (b-v*v /2) * dt ;
		vSqrdt = v * java.lang.Math.sqrt(dt);
		
		if (CallPutFlag.equals("c"))z = 1;
		if (CallPutFlag.equals("p"))z = -1;
		
		for (int i=0 ; i<nSimulations;i++){
			partitions.add(new MonteCarloSteps(viewToUpdate,that,S, Drift, vSqrdt,X, nSteps, z));
		}
		
		
	    	List<Future<Double>> valueOfStocks = null;
			try {
				valueOfStocks = executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	    
	    	for(Future<Double> valueOfAStock : valueOfStocks) {
	        	try {
					Sum = Sum + valueOfAStock.get();
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				} 

	      	}
	    	result = java.lang.Math.exp(-r * T) * (Sum / nSimulations);
	    	viewToUpdate.displayIntermediateValue((System.currentTimeMillis()-timeStart)/1000,labelTime);
	    	viewToUpdate.displayIntermediateValue(result,labelResult);
	        viewToUpdate.displayIntermediateValue(that.countThread(),labelThread);
	    	viewToUpdate.setProgressBar(0);
	    	System.gc();
	}
	
	
	public void getComputerInfo(){
		System.out.println(System.getenv("PROCESSOR_IDENTIFIER"));
		System.out.println(System.getenv("PROCESSOR_ARCHITECTURE"));
		System.out.println("Core ="+System.getenv("NUMBER_OF_PROCESSORS"));
		System.out.println("Total memory ="+Runtime.getRuntime().totalMemory()/1000000+" M");
		System.out.println("Free memory ="+Runtime.getRuntime().freeMemory()/1000000+" M");
		System.out.println("Used memory ="+(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory())/1000000+" M");
		System.out.println("Max memory ="+Runtime.getRuntime().maxMemory()/1000000+" M");
		System.out.println(Thread.activeCount());
		
	}
	
	public void addThreadID(int threadID){
		threadIDMap.putIfAbsent(threadID,0);
	}
	public void addOneSimulation(){
		nbSimuDone++;
	}
	public int getNbSimulationDone(){
		return nbSimuDone;
	}
	public int getNbSimulation(){
		return nSimulations;
	}
	public ConcurrentHashMap<Integer, Integer> getthreadIDMap(){
		return threadIDMap;
	}
	public int countThread(){
		return threadIDMap.size();
	}
	
	public static void main (String [] args){
		MonteCarloGUI view = new MonteCarloGUI();
		JLabel labelResult = new JLabel();
		JLabel labelTime = new JLabel();
		JLabel labelThread = new JLabel();
		MonteCarloSimulation mcs = new MonteCarloSimulation((Updatable)view,labelResult,labelTime,labelThread,"c",40,50,0.5,0.06,0.10,0.45,500,100000);
		
		
		double timeStart = System.currentTimeMillis();
		
		
		try {
			mcs.run();
		} catch (Exception e) {
			e.printStackTrace();
		}
		mcs.getComputerInfo();
		
		double timeEnd = System.currentTimeMillis();

		System.out.println("Time needed : " + (timeEnd-timeStart)/1000 + " s");
		System.out.println("Nb Thread : "+mcs.countThread());
		
	}
	
}
